/*
 * Camera.h
 *
 *  Created on: 2012/07/03
 *      Author: Weihe
 */

#ifndef CAMERA_H_
#define CAMERA_H_

#include "geom.h"
#define M_PI       3.14159265358979323846
#define M_PI_2     1.57079632679489661923
#include <math.h>

namespace glex {

class Lens {
public:
	float focalLength;
	Lens(float focalLength) :
			focalLength(focalLength) {
	}

	static const Lens LENS_50MM;
};

class Film {
public:
	float width, height, aspect;
	Film(float width, float height) :
			width(width), height(height), aspect(width / height) {
	}

	static Film FILM_135;
	static Film FILM_APSC;
	static Film FILM_645;
};

class Camera {
	mutable bool calc;
	mutable Vector4d front;
	mutable Vector4d up;
	mutable Vector4d right;
	Quaternion quad;
	void calcVector() const {
		if (calc) {
			front = quad * Vector4d::Z_AXIS;
			up = quad * Vector4d::Y_AXIS;
			right = quad * Vector4d::X_AXIS;
			calc = false;
		}
	}
public:
	Vector4f position;
	Lens lens;
	Film film;

	Camera();
	Camera(const Camera &value) :
			calc(true), quad(value.quad), position(value.position), lens(
					value.lens), film(value.film) {
	}

	void setupPerspective(int windowWidth, int windowHeight) const;

	void updateCamera() const;

	Quaternion direction() const {
		return quad;
	}

	void direction(Quaternion q) {
		quad = q;
		calc = true;
	}

	void resetRotation() {
		quad = Quaternion::ZEROS;
		front = Vector4d::Z_AXIS;
		up = Vector4d::Y_AXIS;
		right = Vector4d::X_AXIS;
		calc = false;
	}

	const Vector4d& frontVector() const {
		calcVector();
		return front;
	}

	const Vector4d& upVector() const {
		calcVector();
		return up;
	}

	const Vector4d& rightVector() const {
		calcVector();
		return right;
	}

	double pitch() const {
		double dy = frontVector() ^ Vector4d::Y_AXIS;
		return asin(dy);
	}

	double bank() const {
		Vector4d fcy = frontVector().cross(Vector4d::Y_AXIS);
		double costh = fcy ^ upVector();
		return acos(costh) - M_PI_2;
	}

	double heading() const {
		Vector4d dy = Vector4d::Y_AXIS * (frontVector() ^ Vector4d::Y_AXIS);
		Vector4d dx = frontVector() - dy;
		return M_PI_2 - acos(dx ^ Vector4d::X_AXIS);
	}

	void rotHeading(double rad) {
		quad = Quaternion().angleAxis(rad, Vector4d::Y_AXIS) * quad;
		calc = true;
	}

	void roll(double rad) {
		quad = Quaternion().angleAxis(-rad, frontVector()) * quad;
		calc = true;
	}
	void pitch(double rad) {
		quad = Quaternion().angleAxis(-rad, rightVector()) * quad;
		calc = true;
	}
	void yaw(double rad) {
		quad = Quaternion().angleAxis(rad, upVector()) * quad;
		calc = true;
	}

	void shift(float distance, const Vector4d direction) {
		position += direction * distance;
	}
	void shiftUp(float distance) {
		shift(distance, upVector());
	}
	void shiftFront(float distance) {
		shift(distance, frontVector());
	}
	void shiftRight(float distance) {
		shift(distance, rightVector());
	}
};

} /* namespace glex */
#endif /* CAMERA_H_ */
